import axios from 'axios';
import type { AxiosRequestConfig, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
import { cloneDeep } from 'lodash-es';
import type { CreateAxiosOptions, RequestOptions, Result, UploadFileParams } from '@/utils/http/axios/types';
import { AxiosCanceler } from '@/utils/http/axios/axiosCancel';
import { isFunction } from '@/utils/is';
import { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum';

export class CustomAxios {
  private axiosInstance: AxiosInstance;
  private readonly options: CreateAxiosOptions;

  constructor(options: CreateAxiosOptions) {
    this.options = options;
    this.axiosInstance = axios.create(options);
    this.setupInterceptors();
  }

  getAxiosInstance(): AxiosInstance {
    return this.axiosInstance;
  }

  /**
   * @description 重新配置axios
   */
  configAxios(config: CreateAxiosOptions): void {
    if (!this.axiosInstance) {
      return;
    }
    this.createAxios(config);
  }

  /**
   * @description 设置通用header
   * @param headers
   */
  setHeader(headers: any): void {
    if (!this.axiosInstance) {
      return;
    }
    Object.assign(this.axiosInstance.defaults.headers, headers);
  }

  /**
   * @description request请求方法
   * @param config
   * @param options
   */
  request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
    let conf: AxiosRequestConfig = cloneDeep(config);
    const transform = this.getTransform();
    const { requestOptions } = this.options;
    const opt: RequestOptions = Object.assign({}, requestOptions, options ? options : {});
    const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
    if (beforeRequestHook && isFunction(beforeRequestHook)) {
      conf = beforeRequestHook(conf, opt);
    }

    // 这里重置为最新requestOptions
    // @ts-ignore
    conf.requestOptions = opt;

    return new Promise((resolve, reject) => {
      this.axiosInstance.request<any, AxiosResponse<Result>>(conf).then(
        (res: AxiosResponse<Result>) => {
          // 请求是否被取消
          const isCancel = axios.isCancel(res);
          if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
            try {
              const ret = transformRequestData(res, opt);
              resolve(ret);
            } catch (err) {
              reject(err || new Error('request error!'));
            }
            return;
          }
          resolve(res as unknown as Promise<T>);
        },
        (error: Error) => {
          if (requestCatch && isFunction(requestCatch)) {
            reject(requestCatch(error));
            return;
          }
          reject(error);
        }
      );
    });
  }

  get<T = any>(config: AxiosRequestConfig, options: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.GET }, options);
  }

  post<T = any>(config: AxiosRequestConfig, options: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.POST }, options);
  }

  put<T = any>(config: AxiosRequestConfig, options: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.PUT }, options);
  }

  patch<T = any>(config: AxiosRequestConfig, options: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.PATCH }, options);
  }

  delete<T = any>(config: AxiosRequestConfig, options: RequestOptions): Promise<T> {
    return this.request({ ...config, method: RequestEnum.DELETE }, options);
  }

  uploadFile<T>(config: AxiosRequestConfig, params: UploadFileParams) {
    const formData = new window.FormData();
    const customFileName = params.name || 'file';

    if (params.fileName) {
      formData.append(customFileName, params.file, params.fileName);
    } else {
      formData.append(customFileName, params.file);
    }

    if (params.data) {
      Object.keys(params.data).forEach((key) => {
        const value = params.data![key];
        if (Array.isArray(value)) {
          value.forEach((item) => formData.append(`${key}[]`, item));
          return;
        }
        formData.append(key, params.data![key]);
      });
    }

    // @ts-ignore
    return this.axiosInstance.request<T>({
      method: RequestEnum.POST,
      data: formData,
      headers: {
        'Content-type': ContentTypeEnum.FORM_DATA,
        ignoreCancelToken: true,
      },
      ...config,
    });
  }

  /**
   * @description 创建axios实例
   * @param config
   * @private
   */
  private createAxios(config: CreateAxiosOptions): void {
    this.axiosInstance = axios.create(config);
  }

  private getTransform() {
    const { transform } = this.options;
    return transform;
  }

  /**
   * @description 拦截器配置
   */
  private setupInterceptors() {
    const transform = this.getTransform();
    if (!transform) {
      return;
    }
    const { requestInterceptors, requestInterceptorsCatch, responseInterceptors, responseInterceptorsCatch } = transform;
    const axiosCanceler = new AxiosCanceler();

    // 请求拦截处理
    this.axiosInstance.interceptors.request.use(
      (config) => {
        const {
          headers: { ignoreCancelToken },
        } = config;
        let conf = cloneDeep(config);
        const ignoreCancel = ignoreCancelToken !== undefined ? ignoreCancelToken : this.options.requestOptions?.ignoreCancelToken;
        !ignoreCancel && axiosCanceler.addPending(config);
        if (requestInterceptors && isFunction(requestInterceptors)) {
          conf = requestInterceptors(config, this.options);
        }
        return conf;
      },
      (error) => {
        requestInterceptorsCatch && isFunction(requestInterceptorsCatch) && requestInterceptorsCatch(error);
      }
    );

    // 响应拦截处理
    this.axiosInstance.interceptors.response.use(
      (res: AxiosResponse<any>) => {
        res && axiosCanceler.removePending(res.config);
        if (responseInterceptors && isFunction(responseInterceptors)) {
          res = responseInterceptors(res);
        }
        return res;
      },
      (error) => {
        responseInterceptorsCatch && isFunction(responseInterceptorsCatch) && responseInterceptorsCatch(error);
      }
    );
  }
}
